﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using Modbus.Device;
using Modbus.Utility;
using ModbusTestTool;
using System.Xml.Serialization;
using System.Xml;


namespace modBusTest
{
    
    public class modbusmap
    {
        public static int plcOffset = 1;
        

        //private List<mapFields> storage = new List<mapFields>();
        public BindingList<BaseRegister> storage;
        
        private optimizeRange optInputRegRead = new optimizeRange();
        private optimizeRange optHoldRegRead = new optimizeRange();
        private ushort[] holdingRegistersRead = new ushort[10001];
        private ushort[] inputRegistersRead = new ushort[10001];


        

        /// <summary>
        /// Handle to setup up modbus port
        /// </summary>
        public IModbusSerialMaster master { set; get; }
        /// <summary>
        /// slave id with the device you are trying to communicate with
        /// </summary>
        public byte slaveID { set; get; }




        public void readDataXml(string filename)
        {
            storage = new BindingList<BaseRegister>();            
            List<inputDataXml> xmlData = new List<inputDataXml>();
            XmlSerializer xDecoder = new XmlSerializer(typeof(List<inputDataXml>));

            if (File.Exists(filename))
            {
                xmlData = (List<inputDataXml>) xDecoder.Deserialize(File.Open(filename,FileMode.Open,FileAccess.Read,FileShare.ReadWrite));
                foreach (inputDataXml item in xmlData)
                {
                    if (item.paramName != "Reserved" || item.paramName != "")
                    {
                        storage.Add(TypeAdapter.SetupTypemapFields(item.paramName, item.register, item.type));
                    }
                }
            }
            else
            {

                //this is there to provide an example case
                StreamWriter outHandle = new StreamWriter("example.xml");
                xmlData.Add(new inputDataXml());
                xmlData.Add(new inputDataXml());
                xmlData.Add(new inputDataXml());
                xmlData.Add(new inputDataXml());
                xDecoder.Serialize(outHandle, xmlData);

            }

            //populate the opitimize structure
            processHoldingRegister();
            processInputRegister();

            //solve for optimal number of modbus command
            optInputRegRead.solve();
            optHoldRegRead.solve();
            
        }
 
        /// <summary>
        /// Return mapField associated with parameter name
        /// </summary>
        /// <param name="paramName">Name of parameter</param>
        /// <returns>Mapfield structure contating detail about parameter</returns>
        public BaseRegister findParam(string paramName)
        {
            for (int i = 0; i < storage.Count; i++)
            {
                if(storage[i].paramName == paramName){
                    return storage[i];
                }
            }

            throw (new Exception("Param Does Not Exist"));
        }
        /// <summary>
        /// Return mapfield associated with the register
        /// </summary>
        /// <param name="register"></param>
        /// <returns></returns>
        public BaseRegister findParam(int register)
        {
            for (int i = 0; i < storage.Count; i++)
            {
                if (storage[i].register == register)
                {
                    return storage[i];
                }
            }

            throw (new Exception("Param Does Not Exist"));
        }
        /// <summary>
        /// exposes internal data structure for the modbus commands
        /// </summary>
        /// <returns>returns that internal structure</returns>
        public BindingList <BaseRegister> getList()
        {
            return storage;
        }


        /// <summary>
        /// Writes all registers marked dirty via modbus
        /// </summary>
        public void writeDirty()
        {
            optimizeRange InputWrite=new optimizeRange();
            optimizeRange HoldingWrite=new optimizeRange();
            ushort[] holdingRegistersWrite = new ushort[10001];
            ushort[] inputRegistersWrite = new ushort[10001];


            foreach (BaseRegister item in storage)
            {
                FillOptimizeArray(plcOffset, HoldingWrite, item);
                item.getValue(holdingRegistersWrite);
            }
 


            //does the solving and writing
            HoldingWrite.solve();
            
            foreach (pair item in HoldingWrite.getSolution())
            {
                lock (master)
                {

                    try
                    {
                         master.WriteMultipleRegisters(slaveID, (ushort)(item.baseRegister - plcOffset), holdingRegistersWrite.Skip(item.baseRegister).Take(item.range).ToArray());
                    }
                    catch (Exception err)
                    {   
                        throw (new Exception(String.Format("Starting Register: {0} Number of Items: {1} \r\n {2}",item.baseRegister,item.range,err.Message)));
                    }

                }
            }

            for (int i = 0; i < storage.Count; i++)
            {
                storage[i].dirty = false;
            }
        }


        private void processInputRegister()
        {
            populateOptimize(30000, optInputRegRead, BaseRegister.regType.input);
        
        }

        private void processHoldingRegister()
        {
            populateOptimize(40000, optHoldRegRead, BaseRegister.regType.holding);
        }
        /// <summary>
        /// Function will populate optimize range class 
        /// </summary>
        /// <param name="offset">offset is 30000 or 40000 depending on input or
        /// holding register</param>
        /// <param name="regType">passes in the optimizerange class for ether
        /// input or holding register</param>
        private void populateOptimize(int offset, optimizeRange regType, BaseRegister.regType type)
        {
            foreach (BaseRegister item in storage )
            {

                if (item.registerType == type)
                {
                    FillOptimizeArray(offset, regType, item);
                }
            }
        }

        private static void FillOptimizeArray(int offset, optimizeRange regType, BaseRegister item)
        {
            for (int i = 0; i < item.size; i++)
            {
                regType.addRegister((ushort)(item.register - offset + i));

            }
        }
        /// <summary>
        /// need serial modbus port created and initialized and slave id set
        /// Will go and actually read the data from modbus and parse the results
        /// into the database
        /// </summary>
        /// <returns>return the amount of time taken for command in ms</returns>
        public long getDataFromDevice()
        {
            if (master == null || slaveID == 0)
            {
                throw new Exception("Set port handle and slave ID");
            }
            


            master.Transport.ReadTimeout = 1000;

            // time how long this operation takes 
            Stopwatch timer = new Stopwatch();
            timer.Reset();
            timer.Start();

            getInputRegisterValues();
            getHoldingRegisterValues();
            parseResults();
            timer.Stop();

            return timer.ElapsedMilliseconds;
        }
        /// <summary>
        /// function will fetch input register values
        /// </summary>
        private void getInputRegisterValues()
        {   
            foreach (pair item in optInputRegRead.getSolution())
            {
                try
                {
                    ushort[] input;
                    lock (master)
                    {
                         input = master.ReadInputRegisters(slaveID, (ushort)(item.baseRegister - plcOffset), (ushort)item.range);
                    }
                    //this sleep is for redeye's 5ms time delay
                    System.Threading.Thread.Sleep(10);
                    
                    
                    for (int i = 0; i < input.Length; i++)
                    {
                        inputRegistersRead[item.baseRegister+i] = input[i];
                    }
                }
                catch (Exception err)
                {
                    Exception extraData = new Exception(err.Message + " InputRegister BaseRegister: " +item.baseRegister.ToString() + " Range: " + item.range.ToString());
                    throw extraData;
                    
                }

            }
        }
        

        /// <summary>
        /// function will go out and get holding register values
        /// </summary>
        private long  getHoldingRegisterValues()
        {
            Stopwatch test = new Stopwatch();
            test.Reset();
            test.Start();
            foreach (pair item in optHoldRegRead.getSolution())
            {

                try
                {
                    ushort[] input;
                    lock (master)
                    {
                        input = master.ReadHoldingRegisters(slaveID, (ushort)(item.baseRegister - plcOffset), (ushort)item.range);
                    }
                    for (int i = 0; i < input.Length; i++)
                    {
                        holdingRegistersRead[item.baseRegister + i] = input[i];
                    }
                }
                catch (Exception err)
                {
                    Exception extraData = new Exception(err.Message + " Holding BaseRegister: " + item.baseRegister.ToString() + " Range: " + item.range.ToString());
                    throw extraData;
                    
                }
                // time delay to make sure slave device has time to respond
                System.Threading.Thread.Sleep(30);
                
                

            }
            test.Stop();
            return test.ElapsedMilliseconds;
        }

        private void parseResults()
        {
            for (int i=0; i<storage.Count; i++)
            {
                if (storage[i].registerType == BaseRegister.regType.holding)
                {
                    storage[i].setValue(holdingRegistersRead);
                } else
                {
                    storage[i].setValue(inputRegistersRead);
                }
            }
        }
    }
}
